package de.milo.map;

import org.newdawn.slick.Renderable;

import de.milo.map.tile.MapTile;

public class ScalableMapViewer implements Renderable
{
	private Map map;
	private Map viewableMap;
	private ViewportFloat viewport;
	private int tileWidth;
	private int tileHeight;
	private float offsetLeft;
	private float offsetTop;
	private float offsetRight;
	private float offsetBottom;
	private float scaleX;
	private float scaleY;
	private float scaledTileWidth;
	private float scaledTileHeight;
	
	public ScalableMapViewer(Map map, int tileWidth, int tileHeight)
	{
		this.map = map;
		this.viewport = new ViewportFloat();
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		this.scaleX = 1.f;
		this.scaleY = 1.f;
		this.scaledTileWidth = tileWidth * scaleX;
		this.scaledTileHeight = tileHeight * scaleY;
		updateViewableMap();
	}
	
	public int getMapTileWidth()
	{
		return map.getWidth();
	}
	
	public int getMapTileHeight()
	{
		return map.getHeight();
	}
	
	public float getScaledTileWidth()
	{
		return scaledTileWidth;
	}
	
	public float getScaledTileHeight()
	{
		return scaledTileHeight;
	}
	
	public int getTileWidth()
	{
		return tileWidth;
	}
	
	public int getTileHeight()
	{
		return tileHeight;
	}
	
	public float getViewportWidth()
	{
		return viewport.getWidth();
	}
	
	public float getViewportHeight()
	{
		return viewport.getHeight();
	}
	
	public void setScale(float scaleX, float scaleY)
	{
		this.scaleX = scaleX;
		this.scaleY = scaleY;
		scaledTileWidth = tileWidth * scaleX;
		scaledTileHeight = tileHeight * scaleY;
		updateViewableMap();
	}
	
	public void setViewportLocationInPixels(float x, float y)
	{
		viewport.setLocation(x, y);
		updateViewableMap();
	}
	
	public void setViewportLocationInTiles(int x, int y)
	{
		setViewportLocationInPixels(x * scaledTileWidth, y * scaledTileHeight);
	}
	
	public void setViewportInPixels(float x, float y, float width, float height)
	{
		viewport.setLocation(x, y);
		viewport.setSize(width, height);
		updateViewableMap();
	}
	
	public void setViewportInTiles(int x, int y, int width, int height)
	{
		setViewportInPixels(x * scaledTileWidth, y * scaledTileHeight, width * scaledTileWidth, height * scaledTileHeight);
	}
	
	@Override
	public void draw(float x, float y)
	{
		if(viewableMap != null) {
			float imageOffsetLeft = 0.f;
			float imageOffsetTop = 0.f;
			float imageWidth = 0.f;
			float imageHeight = 0.f;
			float nextPosX = 0.f;
			float nextPosY = 0.f;
			MapTile tile = null;
			for(int layer = 0, layerEnd = viewableMap.getLayerCount(); layer < layerEnd; ++layer) {
				for(int column = 0, columnEnd = viewableMap.getWidth(); column < columnEnd; ++column) {
					nextPosX = column * scaledTileWidth - (column == 0 && offsetLeft > 0.f ? 0.f : offsetLeft);
					imageOffsetLeft = (column == 0 && offsetLeft >= 0.f ? offsetLeft : 0.f);
					if(column == 0 && columnEnd > 1) {
						imageWidth = scaledTileWidth - (offsetLeft >= 0 ? offsetLeft : 0);
					} else if(column == columnEnd - 1) {
						imageWidth = scaledTileWidth + offsetRight - imageOffsetLeft;
					} else {
						imageWidth = scaledTileWidth;
					}
					for(int row = 0, rowEnd = viewableMap.getHeight(); row < rowEnd; ++row) {
						tile = viewableMap.getTile(column, row, layer);
						if(tile != null) {
							nextPosY = row * scaledTileHeight - (row == 0 && offsetTop > 0.f ? 0.f : offsetTop);
							imageOffsetTop = (row == 0 && offsetTop >= 0.f ? offsetTop : 0.f);
							if(row == 0 && rowEnd > 1) {
								imageHeight = scaledTileHeight - (offsetTop >= 0 ? offsetTop : 0);
							} else if(row == rowEnd - 1) {
								imageHeight = scaledTileHeight + offsetBottom - imageOffsetTop;
							} else {
								imageHeight = scaledTileHeight;
							}
//							System.out.printf("[%d|%d|%d] -> Width: %.2f Height: %.2f%n", column, row, layer, imageWidth, imageHeight);
							tile.draw(x + nextPosX, y + nextPosY, x + nextPosX + imageWidth, y + nextPosY + imageHeight, (int)imageOffsetLeft, (int)imageOffsetTop, (int)imageWidth, (int)imageHeight);
						}
					}
				}
			}
//			System.out.printf("Offsets %.2f %.2f %.2f %.2f%nEnd of draw.%n", offsetLeft, offsetTop, offsetRight, offsetBottom);
		}
	}
	
	private void updateViewableMap()
	{
		// Determine the coordinates of the visible area in tiles
		// depending on the map.
		// Math.floor is to adjust negative coordinates correctly.
		int tilePositionX = (int)Math.floor(viewport.getX() / scaledTileWidth);
		int tilePositionY = (int)Math.floor(viewport.getY() / scaledTileHeight);
		// Determine how many tiles the visible area shows.
		// Math.ceil is to adjust the width / height correctly if there is
		// only a portion of a tile visible.
		int widthInTiles = (int)Math.ceil(viewport.getWidth() / scaledTileWidth);
		int heightInTiles = (int)Math.ceil(viewport.getHeight() / scaledTileHeight);
		// If one of these conditions is met, then there is no tile inside
		// the visible area of this MapViewer instance.
		if(tilePositionX >= map.getWidth() || tilePositionY >= map.getHeight() || tilePositionX + widthInTiles <= 0 || tilePositionY + heightInTiles <= 0) {
			offsetLeft = offsetTop = offsetRight = offsetBottom = 0.f;
			viewableMap = null;
		} else {
			// If the visible area is to the left of or about the map then
			// the number of tiles the visible area shows needs to be corrected.
			if(tilePositionX < 0) {
				widthInTiles += tilePositionX;
				tilePositionX = 0;
			}
			if(tilePositionY < 0) {
				heightInTiles += tilePositionY;
				tilePositionY = 0;
			}
			// Compute the margin of the images of the tiles in the first row / column.
			offsetLeft = viewport.getX() - (tilePositionX * scaledTileWidth);
			offsetTop = viewport.getY() - (tilePositionY * scaledTileHeight);
			// Compute the margin of the images of the tiles in the first row / column.
			if(tilePositionX + widthInTiles > map.getWidth()) {
				widthInTiles = map.getWidth() - tilePositionX;
				offsetRight = 0.f;
			} else {
				offsetRight = ((tilePositionX + widthInTiles) * scaledTileWidth) - (viewport.getX() + viewport.getWidth());
			}
			if(tilePositionY + heightInTiles > map.getHeight()) {
				heightInTiles = map.getHeight() - tilePositionY;
				offsetBottom = 0.f;
			} else {
				offsetBottom = ((tilePositionY + heightInTiles) * scaledTileHeight) - (viewport.getY() + viewport.getHeight());
			}
			// Finally get all the tiles inside the visible area.
			viewableMap = map.getMapExcerpt(tilePositionX, tilePositionY, widthInTiles, heightInTiles);
		}
	}
}
